<!DOCTYPE html><html lang="zh-CN" data-theme="dark"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>Netty-进阶 | Pei's Blog</title><meta name="keywords" content="Java,Netty"><meta name="author" content="Pei"><meta name="copyright" content="Pei"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#0d0d0d"><meta name="description" content="AI: 这篇文章介绍了一种摘要生成工具，它的主要功能是解释读者发送给它的文本内容。该工具不会换行，每次生成的摘要不会超过200字，它专注于概括文章的主要内容，而不提出建议或指出文章中可能缺少的信息。工具会根据读者提供的输入内容生成简洁而准确的摘要，帮助读者快速了解文章的核心要点。">
<meta property="og:type" content="article">
<meta property="og:title" content="Netty-进阶">
<meta property="og:url" content="https://blog.goku.top/posts/49215.html">
<meta property="og:site_name" content="Pei&#39;s Blog">
<meta property="og:description" content="AI: 这篇文章介绍了一种摘要生成工具，它的主要功能是解释读者发送给它的文本内容。该工具不会换行，每次生成的摘要不会超过200字，它专注于概括文章的主要内容，而不提出建议或指出文章中可能缺少的信息。工具会根据读者提供的输入内容生成简洁而准确的摘要，帮助读者快速了解文章的核心要点。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="http://qiniu.goku.top/blog/netty.png">
<meta property="article:published_time" content="2022-04-11T12:12:16.000Z">
<meta property="article:modified_time" content="2023-10-17T00:12:33.721Z">
<meta property="article:author" content="Pei">
<meta property="article:tag" content="Java">
<meta property="article:tag" content="Netty">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://qiniu.goku.top/blog/netty.png"><link rel="shortcut icon" href="http://qiniu.goku.top/blog/icon.png"><link rel="canonical" href="https://blog.goku.top/posts/49215"><link rel="preconnect" href="//cdn.jsdelivr.net"/><meta name="google-site-verification" content="培的博客"/><meta name="baidu-site-verification" content="培的博客"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.2/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/snackbarjs/1.1.0/snackbar.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/fancyapps-ui/5.0.22/fancybox/fancybox.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"search.xml","languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: undefined,
  noticeOutdate: {"limitDay":365,"position":"top","messagePrev":"该文章已发布","messageNext":"天，文章信息部分有效性请以实际为准。"},
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":400},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: true,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#121212","position":"top-center"},
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: true,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'Netty-进阶',
  isPost: true,
  isHome: false,
  isHighlightShrink: undefined,
  isToc: true,
  postUpdate: '2023-10-17 08:12:33'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="/html/css/plane_v2.css"><link rel="stylesheet" href="/html/css/universe.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" media="defer" onload="this.media='all'"><link rel="stylesheet" href="/html/css/fengche.css"><link rel="stylesheet" href="/html/css/twikoo_beautify.css"><link rel="stylesheet" type="text/css" href="/html/css/heoMainColor.css"><link rel="stylesheet" type="text/css" href="/html/css/poem.css"><link rel="stylesheet" type="text/css" href="/html/css/snackbar.css"><link rel="stylesheet" href="https://cdn1.tianli0.top/gh/zhheo/Post-Abstract-AI@0.15.2/tianli_gpt.css"><link rel="stylesheet" href="/html/css/goku.css"><link rel="stylesheet" href="/html/css/custom_foot.css"><meta name="generator" content="Hexo 6.3.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="https://thirdqq.qlogo.cn/g?b=sdk&amp;k=C88w6ZftQk9ibgdcM4sH4jg&amp;s=140&amp;t=1657271033" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="site-data is-center"><div class="data-item"><a href="/archives/"><div class="headline">文章</div><div class="length-num">38</div></a></div><div class="data-item"><a href="/tags/"><div class="headline">标签</div><div class="length-num">23</div></a></div><div class="data-item"><a href="/categories/"><div class="headline">分类</div><div class="length-num">20</div></a></div></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/random.html"><i class="fa-fw fa-solid fa-shuffle"></i><span> 随机一文</span></a></div><div class="menus_item"><a class="site-page" href="/webstack.html"><i class="fa-fw fa-solid fa-star"></i><span> 网址导航</span></a></div><div class="menus_item"><a class="site-page group hide" href="javascript:void(0);"><i class="fa-fw fa fa-heartbeat"></i><span> 娱乐</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/posts/d10ad42e.html"><span> 堵猫猫</span></a></li><li><a class="site-page child" href="/posts/fc4d392a.html"><span> 烟花秀</span></a></li><li><a class="site-page child" href="/html/TheMatrix/index.html"><span> 黑客帝国</span></a></li><li><a class="site-page child" href="/html/Glare/index.html"><span> 炫光</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/artitalk"><i class="fa-fw fas fa-comment-dots"></i><span> 说说</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于我</span></a></div><div class="menus_item"><a class="site-page group hide" href="javascript:void(0);"><i class="fa-fw fa-solid fa-blog"></i><span> 博客</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></li><li><a class="site-page child" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></li></ul></div></div></div></div><div class="post" id="body-wrap"><header class="not-top-img" id="page-header"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">Pei's Blog</a></span><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/random.html"><i class="fa-fw fa-solid fa-shuffle"></i><span> 随机一文</span></a></div><div class="menus_item"><a class="site-page" href="/webstack.html"><i class="fa-fw fa-solid fa-star"></i><span> 网址导航</span></a></div><div class="menus_item"><a class="site-page group hide" href="javascript:void(0);"><i class="fa-fw fa fa-heartbeat"></i><span> 娱乐</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/posts/d10ad42e.html"><span> 堵猫猫</span></a></li><li><a class="site-page child" href="/posts/fc4d392a.html"><span> 烟花秀</span></a></li><li><a class="site-page child" href="/html/TheMatrix/index.html"><span> 黑客帝国</span></a></li><li><a class="site-page child" href="/html/Glare/index.html"><span> 炫光</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/artitalk"><i class="fa-fw fas fa-comment-dots"></i><span> 说说</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于我</span></a></div><div class="menus_item"><a class="site-page group hide" href="javascript:void(0);"><i class="fa-fw fa-solid fa-blog"></i><span> 博客</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></li><li><a class="site-page child" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></li></ul></div></div></div><div id="nav-right"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i></a></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav></header><main class="layout" id="content-inner"><div id="post"><div id="post-info"><h1 class="post-title">Netty-进阶</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="fa-fw post-meta-icon far fa-calendar-alt"></i><span class="post-meta-label">发表于</span><time datetime="2022-04-11T12:12:16.000Z" title="发表于 2022-04-11 20:12:16">2022-04-11</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/Netty/">Netty</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">10.4k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>53分钟</span></span></div></div></div><article class="post-content" id="article-container"><h1 id="三-Netty-进阶"><a href="#三-Netty-进阶" class="headerlink" title="三. Netty 进阶"></a>三. Netty 进阶</h1><h2 id="1-粘包与半包"><a href="#1-粘包与半包" class="headerlink" title="1. 粘包与半包"></a>1. 粘包与半包</h2><h3 id="1-1-粘包现象"><a href="#1-1-粘包现象" class="headerlink" title="1.1 粘包现象"></a>1.1 粘包现象</h3><p>服务端代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldServer</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldServer.class);</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">start</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">boss</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>(<span class="number">1</span>);</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">ServerBootstrap</span> <span class="variable">serverBootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerBootstrap</span>();</span><br><span class="line">            serverBootstrap.channel(NioServerSocketChannel.class);</span><br><span class="line">            serverBootstrap.group(boss, worker);</span><br><span class="line">            serverBootstrap.childHandler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG));</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;connected &#123;&#125;&quot;</span>, ctx.channel());</span><br><span class="line">                            <span class="built_in">super</span>.channelActive(ctx);</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelInactive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;disconnect &#123;&#125;&quot;</span>, ctx.channel());</span><br><span class="line">                            <span class="built_in">super</span>.channelInactive(ctx);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> serverBootstrap.bind(<span class="number">8080</span>);</span><br><span class="line">            log.debug(<span class="string">&quot;&#123;&#125; binding...&quot;</span>, channelFuture.channel());</span><br><span class="line">            channelFuture.sync();</span><br><span class="line">            log.debug(<span class="string">&quot;&#123;&#125; bound...&quot;</span>, channelFuture.channel());</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;server error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            boss.shutdownGracefully();</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">            log.debug(<span class="string">&quot;stoped&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">HelloWorldServer</span>().start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>客户端代码希望发送 10 个消息，每个消息是 16 字节</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldClient</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldClient.class);</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(worker);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;connetted...&quot;</span>);</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;sending...&quot;</span>);</span><br><span class="line">                            <span class="type">Random</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line">                            <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">                            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">                                <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                                buffer.writeBytes(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">10</span>, <span class="number">11</span>, <span class="number">12</span>, <span class="number">13</span>, <span class="number">14</span>, <span class="number">15</span>&#125;);</span><br><span class="line">                                ctx.writeAndFlush(buffer);</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">8080</span>).sync();</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>服务器端的某次输出，可以看到一次就接收了 160 个字节，而非分 10 次接收</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">08:24:46 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0x81e0fda5] binding...</span><br><span class="line">08:24:46 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0x81e0fda5, L:/0:0:0:0:0:0:0:0:8080] bound...</span><br><span class="line">08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] REGISTERED</span><br><span class="line">08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] ACTIVE</span><br><span class="line">08:24:55 [DEBUG] [nioEventLoopGroup-3-1] c.i.n.HelloWorldServer - connected [id: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177]</span><br><span class="line">08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] READ: 160B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000020| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000030| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000040| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000050| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000060| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000070| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000080| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000090| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] READ COMPLETE</span><br></pre></td></tr></table></figure>



<h3 id="1-2-半包现象"><a href="#1-2-半包现象" class="headerlink" title="1.2 半包现象"></a>1.2 半包现象</h3><p>客户端代码希望发送 1 个消息，这个消息是 160 字节，代码改为</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">    buffer.writeBytes(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">10</span>, <span class="number">11</span>, <span class="number">12</span>, <span class="number">13</span>, <span class="number">14</span>, <span class="number">15</span>&#125;);</span><br><span class="line">&#125;</span><br><span class="line">ctx.writeAndFlush(buffer);</span><br></pre></td></tr></table></figure>

<p>为现象明显，服务端修改一下接收缓冲区，其它代码不变</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">serverBootstrap.option(ChannelOption.SO_RCVBUF, <span class="number">10</span>);</span><br></pre></td></tr></table></figure>

<p>服务器端的某次输出，可以看到接收的消息被分为两节，第一次 20 字节，第二次 140 字节</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">08:43:49 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0x4d6c6a84] binding...</span><br><span class="line">08:43:49 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0x4d6c6a84, L:/0:0:0:0:0:0:0:0:8080] bound...</span><br><span class="line">08:44:23 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] REGISTERED</span><br><span class="line">08:44:23 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] ACTIVE</span><br><span class="line">08:44:23 [DEBUG] [nioEventLoopGroup-3-1] c.i.n.HelloWorldServer - connected [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221]</span><br><span class="line">08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ: 20B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000010| 00 01 02 03                                     |....            |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ COMPLETE</span><br><span class="line">08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ: 140B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000020| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000030| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000040| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000050| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000060| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000070| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000080| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f             |............    |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ COMPLETE</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>注意</strong></p>
<p>serverBootstrap.option(ChannelOption.SO_RCVBUF, 10) 影响的底层接收缓冲区（即滑动窗口）大小，仅决定了 netty 读取的最小单位，netty 实际每次读取的一般是它的整数倍</p>
</blockquote>
<h3 id="1-3-现象分析"><a href="#1-3-现象分析" class="headerlink" title="1.3 现象分析"></a>1.3 现象分析</h3><p>粘包</p>
<ul>
<li>现象，发送 abc def，接收 abcdef</li>
<li>原因<ul>
<li>应用层：接收方 ByteBuf 设置太大（Netty 默认 1024）</li>
<li>滑动窗口：假设发送方 256 bytes 表示一个完整报文，但由于接收方处理不及时且窗口大小足够大，这 256 bytes 字节就会缓冲在接收方的滑动窗口中，当滑动窗口中缓冲了多个报文就会粘包</li>
<li>Nagle 算法：会造成粘包</li>
</ul>
</li>
</ul>
<p>半包</p>
<ul>
<li>现象，发送 abcdef，接收 abc def</li>
<li>原因<ul>
<li>应用层：接收方 ByteBuf 小于实际发送数据量</li>
<li>滑动窗口：假设接收方的窗口只剩了 128 bytes，发送方的报文大小是 256 bytes，这时放不下了，只能先发送前 128 bytes，等待 ack 后才能发送剩余部分，这就造成了半包</li>
<li>MSS 限制：当发送的数据超过 MSS 限制后，会将数据切分发送，就会造成半包</li>
</ul>
</li>
</ul>
<p>本质是因为 TCP 是流式协议，消息无边界</p>
<blockquote>
<p>滑动窗口</p>
<ul>
<li><p>TCP 以一个段（segment）为单位，每发送一个段就需要进行一次确认应答（ack）处理，但如果这么做，缺点是包的往返时间越长性能就越差</p>
<p><img src= "" data-lazy-src="/img/netty/0049.png"></p>
</li>
<li><p>为了解决此问题，引入了窗口概念，窗口大小即决定了无需等待应答而可以继续发送的数据最大值</p>
<p><img src= "" data-lazy-src="/img/netty/0051.png"></p>
</li>
<li><p>窗口实际就起到一个缓冲区的作用，同时也能起到流量控制的作用</p>
<ul>
<li>图中深色的部分即要发送的数据，高亮的部分即窗口</li>
<li>窗口内的数据才允许被发送，当应答未到达前，窗口必须停止滑动</li>
<li>如果 1001~2000 这个段的数据 ack 回来了，窗口就可以向前滑动</li>
<li>接收方也会维护一个窗口，只有落在窗口内的数据才能允许接收</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<p> MSS 限制</p>
<ul>
<li><p>链路层对一次能够发送的最大数据有限制，这个限制称之为 MTU（maximum transmission unit），不同的链路设备的 MTU 值也有所不同，例如</p>
</li>
<li><p>以太网的 MTU 是 1500</p>
</li>
<li><p>FDDI（光纤分布式数据接口）的 MTU 是 4352</p>
</li>
<li><p>本地回环地址的 MTU 是 65535 - 本地测试不走网卡</p>
</li>
<li><p>MSS 是最大段长度（maximum segment size），它是 MTU 刨去 tcp 头和 ip 头后剩余能够作为数据传输的字节数</p>
</li>
<li><p>ipv4 tcp 头占用 20 bytes，ip 头占用 20 bytes，因此以太网 MSS 的值为 1500 - 40 &#x3D; 1460</p>
</li>
<li><p>TCP 在传递大量数据时，会按照 MSS 大小将数据进行分割发送</p>
</li>
<li><p>MSS 的值在三次握手时通知对方自己 MSS 的值，然后在两者之间选择一个小值作为 MSS</p>
</li>
</ul>
<p>  <img src= "" data-lazy-src="/img/netty/0031.jpg" alt="图示"></p>
</blockquote>
<blockquote>
<p>Nagle 算法</p>
<ul>
<li>即使发送一个字节，也需要加入 tcp 头和 ip 头，也就是总字节数会使用 41 bytes，非常不经济。因此为了提高网络利用率，tcp 希望尽可能发送足够大的数据，这就是 Nagle 算法产生的缘由</li>
<li>该算法是指发送端即使还有应该发送的数据，但如果这部分数据很少的话，则进行延迟发送<ul>
<li>如果 SO_SNDBUF 的数据达到 MSS，则需要发送</li>
<li>如果 SO_SNDBUF 中含有 FIN（表示需要连接关闭）这时将剩余数据发送，再关闭</li>
<li>如果 TCP_NODELAY &#x3D; true，则需要发送</li>
<li>已发送的数据都收到 ack 时，则需要发送</li>
<li>上述条件不满足，但发生超时（一般为 200ms）则需要发送</li>
<li>除上述情况，延迟发送</li>
</ul>
</li>
</ul>
</blockquote>
<h3 id="1-4-解决方案"><a href="#1-4-解决方案" class="headerlink" title="1.4 解决方案"></a>1.4 解决方案</h3><ol>
<li>短链接，发一个包建立一次连接，这样连接建立到连接断开之间就是消息的边界，缺点效率太低</li>
<li>每一条消息采用固定长度，缺点浪费空间</li>
<li>每一条消息采用分隔符，例如 \n，缺点需要转义</li>
<li>每一条消息分为 head 和 body，head 中包含 body 的长度</li>
</ol>
<h4 id="方法1，短链接"><a href="#方法1，短链接" class="headerlink" title="方法1，短链接"></a>方法1，短链接</h4><p>以解决粘包为例</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldClient</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldClient.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 分 10 次发送</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            send();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">send</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(worker);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;conneted...&quot;</span>);</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG));</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;sending...&quot;</span>);</span><br><span class="line">                            <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                            buffer.writeBytes(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">10</span>, <span class="number">11</span>, <span class="number">12</span>, <span class="number">13</span>, <span class="number">14</span>, <span class="number">15</span>&#125;);</span><br><span class="line">                            ctx.writeAndFlush(buffer);</span><br><span class="line">                            <span class="comment">// 发完即关</span></span><br><span class="line">                            ctx.close();</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>).sync();</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出，略</p>
<blockquote>
<p>半包用这种办法还是不好解决，因为接收方的缓冲区大小是有限的</p>
</blockquote>
<h4 id="方法2，固定长度"><a href="#方法2，固定长度" class="headerlink" title="方法2，固定长度"></a>方法2，固定长度</h4><p>让所有数据包长度固定（假设长度为 8 字节），服务器端加入</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">FixedLengthFrameDecoder</span>(<span class="number">8</span>));</span><br></pre></td></tr></table></figure>

<p>客户端测试代码，注意, 采用这种方法后，客户端什么时候 flush 都可以</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldClient</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldClient.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(worker);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;connetted...&quot;</span>);</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG));</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;sending...&quot;</span>);</span><br><span class="line">                            <span class="comment">// 发送内容随机的数据包</span></span><br><span class="line">                            <span class="type">Random</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line">                            <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">                            <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">                                <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">8</span>];</span><br><span class="line">                                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; r.nextInt(<span class="number">8</span>); j++) &#123;</span><br><span class="line">                                    bytes[j] = (<span class="type">byte</span>) c;</span><br><span class="line">                                &#125;</span><br><span class="line">                                c++;</span><br><span class="line">                                buffer.writeBytes(bytes);</span><br><span class="line">                            &#125;</span><br><span class="line">                            ctx.writeAndFlush(buffer);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;192.168.0.103&quot;</span>, <span class="number">9090</span>).sync();</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>客户端输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - connetted...</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x3c2ef3c2] REGISTERED</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x3c2ef3c2] CONNECT: /192.168.0.103:9090</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x3c2ef3c2, L:/192.168.0.103:53155 - R:/192.168.0.103:9090] ACTIVE</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - sending...</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x3c2ef3c2, L:/192.168.0.103:53155 - R:/192.168.0.103:9090] WRITE: 80B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61 61 61 61 00 00 00 00 62 00 00 00 00 00 00 00 |aaaa....b.......|</span><br><span class="line">|00000010| 63 63 00 00 00 00 00 00 64 00 00 00 00 00 00 00 |cc......d.......|</span><br><span class="line">|00000020| 00 00 00 00 00 00 00 00 66 66 66 66 00 00 00 00 |........ffff....|</span><br><span class="line">|00000030| 67 67 67 00 00 00 00 00 68 00 00 00 00 00 00 00 |ggg.....h.......|</span><br><span class="line">|00000040| 69 69 69 69 69 00 00 00 6a 6a 6a 6a 00 00 00 00 |iiiii...jjjj....|</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x3c2ef3c2, L:/192.168.0.103:53155 - R:/192.168.0.103:9090] FLUSH</span><br></pre></td></tr></table></figure>

<p>服务端输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">12:06:51 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0xe3d9713f] binding...</span><br><span class="line">12:06:51 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0xe3d9713f, L:/192.168.0.103:9090] bound...</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] REGISTERED</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] ACTIVE</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] c.i.n.HelloWorldServer - connected [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155]</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61 61 61 61 00 00 00 00                         |aaaa....        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 62 00 00 00 00 00 00 00                         |b.......        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 63 63 00 00 00 00 00 00                         |cc......        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 64 00 00 00 00 00 00 00                         |d.......        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 00 00 00 00 00 00 00 00                         |........        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 66 66 66 66 00 00 00 00                         |ffff....        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 67 67 67 00 00 00 00 00                         |ggg.....        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 68 00 00 00 00 00 00 00                         |h.......        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 69 69 69 69 69 00 00 00                         |iiiii...        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 6a 6a 6a 6a 00 00 00 00                         |jjjj....        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ COMPLETE</span><br></pre></td></tr></table></figure>

<p>缺点是，数据包的大小不好把握</p>
<ul>
<li>长度定的太大，浪费</li>
<li>长度定的太小，对某些数据包又显得不够</li>
</ul>
<h4 id="方法3，固定分隔符"><a href="#方法3，固定分隔符" class="headerlink" title="方法3，固定分隔符"></a>方法3，固定分隔符</h4><p>服务端加入，默认以 \n 或 \r\n 作为分隔符，如果超出指定长度仍未出现分隔符，则抛出异常</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LineBasedFrameDecoder</span>(<span class="number">1024</span>));</span><br></pre></td></tr></table></figure>

<p>客户端在每条消息之后，加入 \n 分隔符</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldClient</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldClient.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(worker);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;connetted...&quot;</span>);</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG));</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;sending...&quot;</span>);</span><br><span class="line">                            <span class="type">Random</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line">                            <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">                            <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">                                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt;= r.nextInt(<span class="number">16</span>)+<span class="number">1</span>; j++) &#123;</span><br><span class="line">                                    buffer.writeByte((<span class="type">byte</span>) c);</span><br><span class="line">                                &#125;</span><br><span class="line">                                buffer.writeByte(<span class="number">10</span>);</span><br><span class="line">                                c++;</span><br><span class="line">                            &#125;</span><br><span class="line">                            ctx.writeAndFlush(buffer);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;192.168.0.103&quot;</span>, <span class="number">9090</span>).sync();</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>客户端输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - connetted...</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x1282d755] REGISTERED</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x1282d755] CONNECT: /192.168.0.103:9090</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x1282d755, L:/192.168.0.103:63641 - R:/192.168.0.103:9090] ACTIVE</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - sending...</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x1282d755, L:/192.168.0.103:63641 - R:/192.168.0.103:9090] WRITE: 60B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61 0a 62 62 62 0a 63 63 63 0a 64 64 0a 65 65 65 |a.bbb.ccc.dd.eee|</span><br><span class="line">|00000010| 65 65 65 65 65 65 65 0a 66 66 0a 67 67 67 67 67 |eeeeeee.ff.ggggg|</span><br><span class="line">|00000020| 67 67 0a 68 68 68 68 0a 69 69 69 69 69 69 69 0a |gg.hhhh.iiiiiii.|</span><br><span class="line">|00000030| 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a 0a             |jjjjjjjjjjj.    |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x1282d755, L:/192.168.0.103:63641 - R:/192.168.0.103:9090] FLUSH</span><br></pre></td></tr></table></figure>



<p>服务端输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] c.i.n.HelloWorldServer - connected [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641]</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 1B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61                                              |a               |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 3B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 62 62 62                                        |bbb             |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 3B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 63 63 63                                        |ccc             |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 2B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 64 64                                           |dd              |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 10B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 65 65 65 65 65 65 65 65 65 65                   |eeeeeeeeee      |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 2B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 66 66                                           |ff              |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 7B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 67 67 67 67 67 67 67                            |ggggggg         |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 4B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 68 68 68 68                                     |hhhh            |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 7B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 69 69 69 69 69 69 69                            |iiiiiii         |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 11B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a                |jjjjjjjjjjj     |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [id: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ COMPLETE</span><br></pre></td></tr></table></figure>

<p>缺点，处理字符数据比较合适，但如果内容本身包含了分隔符（字节数据常常会有此情况），那么就会解析错误</p>
<h4 id="方法4，预设长度"><a href="#方法4，预设长度" class="headerlink" title="方法4，预设长度"></a>方法4，预设长度</h4><p>在发送消息前，先约定用定长字节表示接下来数据的长度</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 最大长度，长度偏移，长度占用字节，长度调整，剥离字节数</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LengthFieldBasedFrameDecoder</span>(<span class="number">1024</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">1</span>));</span><br></pre></td></tr></table></figure>

<p>客户端代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldClient</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldClient.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(worker);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;connetted...&quot;</span>);</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG));</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;sending...&quot;</span>);</span><br><span class="line">                            <span class="type">Random</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line">                            <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">                            <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">                                <span class="type">byte</span> <span class="variable">length</span> <span class="operator">=</span> (<span class="type">byte</span>) (r.nextInt(<span class="number">16</span>) + <span class="number">1</span>);</span><br><span class="line">                                <span class="comment">// 先写入长度</span></span><br><span class="line">                                buffer.writeByte(length);</span><br><span class="line">                                <span class="comment">// 再</span></span><br><span class="line">                                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt;= length; j++) &#123;</span><br><span class="line">                                    buffer.writeByte((<span class="type">byte</span>) c);</span><br><span class="line">                                &#125;</span><br><span class="line">                                c++;</span><br><span class="line">                            &#125;</span><br><span class="line">                            ctx.writeAndFlush(buffer);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;192.168.0.103&quot;</span>, <span class="number">9090</span>).sync();</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>客户端输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - connetted...</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0xf0f347b8] REGISTERED</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0xf0f347b8] CONNECT: /192.168.0.103:9090</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0xf0f347b8, L:/192.168.0.103:49979 - R:/192.168.0.103:9090] ACTIVE</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - sending...</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0xf0f347b8, L:/192.168.0.103:49979 - R:/192.168.0.103:9090] WRITE: 97B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 09 61 61 61 61 61 61 61 61 61 09 62 62 62 62 62 |.aaaaaaaaa.bbbbb|</span><br><span class="line">|00000010| 62 62 62 62 06 63 63 63 63 63 63 08 64 64 64 64 |bbbb.cccccc.dddd|</span><br><span class="line">|00000020| 64 64 64 64 0f 65 65 65 65 65 65 65 65 65 65 65 |dddd.eeeeeeeeeee|</span><br><span class="line">|00000030| 65 65 65 65 0d 66 66 66 66 66 66 66 66 66 66 66 |eeee.fffffffffff|</span><br><span class="line">|00000040| 66 66 02 67 67 02 68 68 0e 69 69 69 69 69 69 69 |ff.gg.hh.iiiiiii|</span><br><span class="line">|00000050| 69 69 69 69 69 69 69 09 6a 6a 6a 6a 6a 6a 6a 6a |iiiiiii.jjjjjjjj|</span><br><span class="line">|00000060| 6a                                              |j               |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0xf0f347b8, L:/192.168.0.103:49979 - R:/192.168.0.103:9090] FLUSH</span><br></pre></td></tr></table></figure>



<p>服务端输出</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line">14:36:50 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0xdff439d3] binding...</span><br><span class="line">14:36:51 [DEBUG] [main] c.i.n.HelloWorldServer - [id: 0xdff439d3, L:/192.168.0.103:9090] bound...</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] REGISTERED</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] ACTIVE</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] c.i.n.HelloWorldServer - connected [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979]</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 9B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61 61 61 61 61 61 61 61 61                      |aaaaaaaaa       |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 9B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 62 62 62 62 62 62 62 62 62                      |bbbbbbbbb       |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 6B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 63 63 63 63 63 63                               |cccccc          |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 64 64 64 64 64 64 64 64                         |dddddddd        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 15B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65    |eeeeeeeeeeeeeee |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 13B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 66 66 66 66 66 66 66 66 66 66 66 66 66          |fffffffffffff   |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 2B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 67 67                                           |gg              |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 2B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 68 68                                           |hh              |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 14B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 69 69 69 69 69 69 69 69 69 69 69 69 69 69       |iiiiiiiiiiiiii  |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 9B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 6a 6a 6a 6a 6a 6a 6a 6a 6a                      |jjjjjjjjj       |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ COMPLETE</span><br><span class="line"></span><br></pre></td></tr></table></figure>



<h2 id="2-协议设计与解析"><a href="#2-协议设计与解析" class="headerlink" title="2. 协议设计与解析"></a>2. 协议设计与解析</h2><h3 id="2-1-为什么需要协议？"><a href="#2-1-为什么需要协议？" class="headerlink" title="2.1 为什么需要协议？"></a>2.1 为什么需要协议？</h3><p>TCP&#x2F;IP 中消息传输基于流的方式，没有边界。</p>
<p>协议的目的就是划定消息的边界，制定通信双方要共同遵守的通信规则</p>
<p>例如：在网络上传输</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">下雨天留客天留我不留</span><br></pre></td></tr></table></figure>

<p>是中文一句著名的无标点符号句子，在没有标点符号情况下，这句话有数种拆解方式，而意思却是完全不同，所以常被用作讲述标点符号的重要性</p>
<p>一种解读</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">下雨天留客，天留，我不留</span><br></pre></td></tr></table></figure>

<p>另一种解读</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">下雨天，留客天，留我不？留</span><br></pre></td></tr></table></figure>



<p>如何设计协议呢？其实就是给网络传输的信息加上“标点符号”。但通过分隔符来断句不是很好，因为分隔符本身如果用于传输，那么必须加以区分。因此，下面一种协议较为常用</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">定长字节表示内容长度 + 实际内容</span><br></pre></td></tr></table></figure>

<p>例如，假设一个中文字符长度为 3，按照上述协议的规则，发送信息方式如下，就不会被接收方弄错意思了</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">0f下雨天留客06天留09我不留</span><br></pre></td></tr></table></figure>



<blockquote>
<p>小故事</p>
<p>很久很久以前，一位私塾先生到一家任教。双方签订了一纸协议：“无鸡鸭亦可无鱼肉亦可白菜豆腐不可少不得束修金”。此后，私塾先生虽然认真教课，但主人家则总是给私塾先生以白菜豆腐为菜，丝毫未见鸡鸭鱼肉的款待。私塾先生先是很不解，可是后来也就想通了：主人把鸡鸭鱼肉的钱都会换为束修金的，也罢。至此双方相安无事。</p>
<p>年关将至，一个学年段亦告结束。私塾先生临行时，也不见主人家为他交付束修金，遂与主家理论。然主家亦振振有词：“有协议为证——无鸡鸭亦可，无鱼肉亦可，白菜豆腐不可少，不得束修金。这白纸黑字明摆着的，你有什么要说的呢？”</p>
<p>私塾先生据理力争：“协议是这样的——无鸡，鸭亦可；无鱼，肉亦可；白菜豆腐不可，少不得束修金。”</p>
<p>双方唇枪舌战，你来我往，真个是不亦乐乎！</p>
<p>这里的束修金，也作“束脩”，应当是泛指教师应当得到的报酬</p>
</blockquote>
<h3 id="2-2-redis-协议举例"><a href="#2-2-redis-协议举例" class="headerlink" title="2.2 redis 协议举例"></a>2.2 redis 协议举例</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line"><span class="type">byte</span>[] LINE = &#123;<span class="number">13</span>, <span class="number">10</span>&#125;;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">    bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">    bootstrap.group(worker);</span><br><span class="line">    bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> &#123;</span><br><span class="line">            ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>());</span><br><span class="line">            ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                <span class="comment">// 会在连接 channel 建立成功后，会触发 active 事件</span></span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> &#123;</span><br><span class="line">                    set(ctx);</span><br><span class="line">                    get(ctx);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">get</span><span class="params">(ChannelHandlerContext ctx)</span> &#123;</span><br><span class="line">                    <span class="type">ByteBuf</span> <span class="variable">buf</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;*2&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;$3&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;get&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;$3&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;aaa&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    ctx.writeAndFlush(buf);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(ChannelHandlerContext ctx)</span> &#123;</span><br><span class="line">                    <span class="type">ByteBuf</span> <span class="variable">buf</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;*3&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;$3&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;set&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;$3&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;aaa&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;$3&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    buf.writeBytes(<span class="string">&quot;bbb&quot;</span>.getBytes());</span><br><span class="line">                    buf.writeBytes(LINE);</span><br><span class="line">                    ctx.writeAndFlush(buf);</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    <span class="type">ByteBuf</span> <span class="variable">buf</span> <span class="operator">=</span> (ByteBuf) msg;</span><br><span class="line">                    System.out.println(buf.toString(Charset.defaultCharset()));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;localhost&quot;</span>, <span class="number">6379</span>).sync();</span><br><span class="line">    channelFuture.channel().closeFuture().sync();</span><br><span class="line">&#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">    log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    worker.shutdownGracefully();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-3-http-协议举例"><a href="#2-3-http-协议举例" class="headerlink" title="2.3 http 协议举例"></a>2.3 http 协议举例</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">NioEventLoopGroup</span> <span class="variable">boss</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line"><span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="type">ServerBootstrap</span> <span class="variable">serverBootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerBootstrap</span>();</span><br><span class="line">    serverBootstrap.channel(NioServerSocketChannel.class);</span><br><span class="line">    serverBootstrap.group(boss, worker);</span><br><span class="line">    serverBootstrap.childHandler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">            ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG));</span><br><span class="line">            ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">HttpServerCodec</span>());</span><br><span class="line">            ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;HttpRequest&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, HttpRequest msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    <span class="comment">// 获取请求</span></span><br><span class="line">                    log.debug(msg.uri());</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 返回响应</span></span><br><span class="line">                    <span class="type">DefaultFullHttpResponse</span> <span class="variable">response</span> <span class="operator">=</span></span><br><span class="line">                            <span class="keyword">new</span> <span class="title class_">DefaultFullHttpResponse</span>(msg.protocolVersion(), HttpResponseStatus.OK);</span><br><span class="line"></span><br><span class="line">                    <span class="type">byte</span>[] bytes = <span class="string">&quot;&lt;h1&gt;Hello, world!&lt;/h1&gt;&quot;</span>.getBytes();</span><br><span class="line"></span><br><span class="line">                    response.headers().setInt(CONTENT_LENGTH, bytes.length);</span><br><span class="line">                    response.content().writeBytes(bytes);</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 写回响应</span></span><br><span class="line">                    ctx.writeAndFlush(response);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="comment">/*ch.pipeline().addLast(new ChannelInboundHandlerAdapter() &#123;</span></span><br><span class="line"><span class="comment">                @Override</span></span><br><span class="line"><span class="comment">                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception &#123;</span></span><br><span class="line"><span class="comment">                    log.debug(&quot;&#123;&#125;&quot;, msg.getClass());</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">                    if (msg instanceof HttpRequest) &#123; // 请求行，请求头</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">                    &#125; else if (msg instanceof HttpContent) &#123; //请求体</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">                    &#125;</span></span><br><span class="line"><span class="comment">                &#125;</span></span><br><span class="line"><span class="comment">            &#125;);*/</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> serverBootstrap.bind(<span class="number">8080</span>).sync();</span><br><span class="line">    channelFuture.channel().closeFuture().sync();</span><br><span class="line">&#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">    log.error(<span class="string">&quot;server error&quot;</span>, e);</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    boss.shutdownGracefully();</span><br><span class="line">    worker.shutdownGracefully();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-4-自定义协议要素"><a href="#2-4-自定义协议要素" class="headerlink" title="2.4 自定义协议要素"></a>2.4 自定义协议要素</h3><ul>
<li>魔数，用来在第一时间判定是否是无效数据包</li>
<li>版本号，可以支持协议的升级</li>
<li>序列化算法，消息正文到底采用哪种序列化反序列化方式，可以由此扩展，例如：json、protobuf、hessian、jdk</li>
<li>指令类型，是登录、注册、单聊、群聊… 跟业务相关</li>
<li>请求序号，为了双工通信，提供异步能力</li>
<li>正文长度</li>
<li>消息正文</li>
</ul>
<h4 id="编解码器"><a href="#编解码器" class="headerlink" title="编解码器"></a>编解码器</h4><p>根据上面的要素，设计一个登录请求消息和登录响应消息，并使用 Netty 完成收发</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MessageCodec</span> <span class="keyword">extends</span> <span class="title class_">ByteToMessageCodec</span>&lt;Message&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">encode</span><span class="params">(ChannelHandlerContext ctx, Message msg, ByteBuf out)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 1. 4 字节的魔数</span></span><br><span class="line">        out.writeBytes(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>&#125;);</span><br><span class="line">        <span class="comment">// 2. 1 字节的版本,</span></span><br><span class="line">        out.writeByte(<span class="number">1</span>);</span><br><span class="line">        <span class="comment">// 3. 1 字节的序列化方式 jdk 0 , json 1</span></span><br><span class="line">        out.writeByte(<span class="number">0</span>);</span><br><span class="line">        <span class="comment">// 4. 1 字节的指令类型</span></span><br><span class="line">        out.writeByte(msg.getMessageType());</span><br><span class="line">        <span class="comment">// 5. 4 个字节</span></span><br><span class="line">        out.writeInt(msg.getSequenceId());</span><br><span class="line">        <span class="comment">// 无意义，对齐填充</span></span><br><span class="line">        out.writeByte(<span class="number">0xff</span>);</span><br><span class="line">        <span class="comment">// 6. 获取内容的字节数组</span></span><br><span class="line">        <span class="type">ByteArrayOutputStream</span> <span class="variable">bos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayOutputStream</span>();</span><br><span class="line">        <span class="type">ObjectOutputStream</span> <span class="variable">oos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectOutputStream</span>(bos);</span><br><span class="line">        oos.writeObject(msg);</span><br><span class="line">        <span class="type">byte</span>[] bytes = bos.toByteArray();</span><br><span class="line">        <span class="comment">// 7. 长度</span></span><br><span class="line">        out.writeInt(bytes.length);</span><br><span class="line">        <span class="comment">// 8. 写入内容</span></span><br><span class="line">        out.writeBytes(bytes);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">decode</span><span class="params">(ChannelHandlerContext ctx, ByteBuf in, List&lt;Object&gt; out)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">magicNum</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">version</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">serializerType</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">messageType</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">int</span> <span class="variable">sequenceId</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        in.readByte();</span><br><span class="line">        <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[length];</span><br><span class="line">        in.readBytes(bytes, <span class="number">0</span>, length);</span><br><span class="line">        <span class="type">ObjectInputStream</span> <span class="variable">ois</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectInputStream</span>(<span class="keyword">new</span> <span class="title class_">ByteArrayInputStream</span>(bytes));</span><br><span class="line">        <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> (Message) ois.readObject();</span><br><span class="line">        log.debug(<span class="string">&quot;&#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;&quot;</span>, magicNum, version, serializerType, messageType, sequenceId, length);</span><br><span class="line">        log.debug(<span class="string">&quot;&#123;&#125;&quot;</span>, message);</span><br><span class="line">        out.add(message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>测试</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">EmbeddedChannel</span> <span class="variable">channel</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">EmbeddedChannel</span>(</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">LengthFieldBasedFrameDecoder</span>(</span><br><span class="line">        <span class="number">1024</span>, <span class="number">12</span>, <span class="number">4</span>, <span class="number">0</span>, <span class="number">0</span>),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">MessageCodec</span>()</span><br><span class="line">);</span><br><span class="line"><span class="comment">// encode</span></span><br><span class="line"><span class="type">LoginRequestMessage</span> <span class="variable">message</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LoginRequestMessage</span>(<span class="string">&quot;zhangsan&quot;</span>, <span class="string">&quot;123&quot;</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line"><span class="comment">//        channel.writeOutbound(message);</span></span><br><span class="line"><span class="comment">// decode</span></span><br><span class="line"><span class="type">ByteBuf</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBufAllocator.DEFAULT.buffer();</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">MessageCodec</span>().encode(<span class="literal">null</span>, message, buf);</span><br><span class="line"></span><br><span class="line"><span class="type">ByteBuf</span> <span class="variable">s1</span> <span class="operator">=</span> buf.slice(<span class="number">0</span>, <span class="number">100</span>);</span><br><span class="line"><span class="type">ByteBuf</span> <span class="variable">s2</span> <span class="operator">=</span> buf.slice(<span class="number">100</span>, buf.readableBytes() - <span class="number">100</span>);</span><br><span class="line">s1.retain(); <span class="comment">// 引用计数 2</span></span><br><span class="line">channel.writeInbound(s1); <span class="comment">// release 1</span></span><br><span class="line">channel.writeInbound(s2);</span><br></pre></td></tr></table></figure>



<p>解读</p>
<p><img src= "" data-lazy-src="/img/netty/0013.png"></p>
<h4 id="💡-什么时候可以加-Sharable"><a href="#💡-什么时候可以加-Sharable" class="headerlink" title="💡 什么时候可以加 @Sharable"></a>💡 什么时候可以加 @Sharable</h4><ul>
<li>当 handler 不保存状态时，就可以安全地在多线程下被共享</li>
<li>但要注意对于编解码器类，不能继承 ByteToMessageCodec 或 CombinedChannelDuplexHandler 父类，他们的构造方法对 @Sharable 有限制</li>
<li>如果能确保编解码器不会保存状态，可以继承 MessageToMessageCodec 父类</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 必须和 LengthFieldBasedFrameDecoder 一起使用，确保接到的 ByteBuf 消息是完整的</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MessageCodecSharable</span> <span class="keyword">extends</span> <span class="title class_">MessageToMessageCodec</span>&lt;ByteBuf, Message&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">encode</span><span class="params">(ChannelHandlerContext ctx, Message msg, List&lt;Object&gt; outList)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">ByteBuf</span> <span class="variable">out</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">        <span class="comment">// 1. 4 字节的魔数</span></span><br><span class="line">        out.writeBytes(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>&#125;);</span><br><span class="line">        <span class="comment">// 2. 1 字节的版本,</span></span><br><span class="line">        out.writeByte(<span class="number">1</span>);</span><br><span class="line">        <span class="comment">// 3. 1 字节的序列化方式 jdk 0 , json 1</span></span><br><span class="line">        out.writeByte(<span class="number">0</span>);</span><br><span class="line">        <span class="comment">// 4. 1 字节的指令类型</span></span><br><span class="line">        out.writeByte(msg.getMessageType());</span><br><span class="line">        <span class="comment">// 5. 4 个字节</span></span><br><span class="line">        out.writeInt(msg.getSequenceId());</span><br><span class="line">        <span class="comment">// 无意义，对齐填充</span></span><br><span class="line">        out.writeByte(<span class="number">0xff</span>);</span><br><span class="line">        <span class="comment">// 6. 获取内容的字节数组</span></span><br><span class="line">        <span class="type">ByteArrayOutputStream</span> <span class="variable">bos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayOutputStream</span>();</span><br><span class="line">        <span class="type">ObjectOutputStream</span> <span class="variable">oos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectOutputStream</span>(bos);</span><br><span class="line">        oos.writeObject(msg);</span><br><span class="line">        <span class="type">byte</span>[] bytes = bos.toByteArray();</span><br><span class="line">        <span class="comment">// 7. 长度</span></span><br><span class="line">        out.writeInt(bytes.length);</span><br><span class="line">        <span class="comment">// 8. 写入内容</span></span><br><span class="line">        out.writeBytes(bytes);</span><br><span class="line">        outList.add(out);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">decode</span><span class="params">(ChannelHandlerContext ctx, ByteBuf in, List&lt;Object&gt; out)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">magicNum</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">version</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">serializerType</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">messageType</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">int</span> <span class="variable">sequenceId</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        in.readByte();</span><br><span class="line">        <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[length];</span><br><span class="line">        in.readBytes(bytes, <span class="number">0</span>, length);</span><br><span class="line">        <span class="type">ObjectInputStream</span> <span class="variable">ois</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectInputStream</span>(<span class="keyword">new</span> <span class="title class_">ByteArrayInputStream</span>(bytes));</span><br><span class="line">        <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> (Message) ois.readObject();</span><br><span class="line">        log.debug(<span class="string">&quot;&#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;&quot;</span>, magicNum, version, serializerType, messageType, sequenceId, length);</span><br><span class="line">        log.debug(<span class="string">&quot;&#123;&#125;&quot;</span>, message);</span><br><span class="line">        out.add(message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="3-聊天室案例"><a href="#3-聊天室案例" class="headerlink" title="3. 聊天室案例"></a>3. 聊天室案例</h2><h3 id="3-1-聊天室业务介绍"><a href="#3-1-聊天室业务介绍" class="headerlink" title="3.1 聊天室业务介绍"></a>3.1 聊天室业务介绍</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 用户管理接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">UserService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 登录</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> username 用户名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> password 密码</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 登录成功返回 true, 否则返回 false</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">login</span><span class="params">(String username, String password)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 会话管理接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Session</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 绑定会话</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> channel 哪个 channel 要绑定会话</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> username 会话绑定用户</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">bind</span><span class="params">(Channel channel, String username)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 解绑会话</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> channel 哪个 channel 要解绑会话</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">unbind</span><span class="params">(Channel channel)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取属性</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> channel 哪个 channel</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 属性名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 属性值</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Object <span class="title function_">getAttribute</span><span class="params">(Channel channel, String name)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 设置属性</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> channel 哪个 channel</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 属性名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> value 属性值</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">setAttribute</span><span class="params">(Channel channel, String name, Object value)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据用户名获取 channel</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> username 用户名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> channel</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Channel <span class="title function_">getChannel</span><span class="params">(String username)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 聊天组会话管理接口</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">GroupSession</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 创建一个聊天组, 如果不存在才能创建成功, 否则返回 null</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> members 成员</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 成功时返回组对象, 失败返回 null</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Group <span class="title function_">createGroup</span><span class="params">(String name, Set&lt;String&gt; members)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 加入聊天组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> member 成员名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 如果组不存在返回 null, 否则返回组对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Group <span class="title function_">joinMember</span><span class="params">(String name, String member)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 移除组成员</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> member 成员名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 如果组不存在返回 null, 否则返回组对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Group <span class="title function_">removeMember</span><span class="params">(String name, String member)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 移除聊天组</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 如果组不存在返回 null, 否则返回组对象</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Group <span class="title function_">removeGroup</span><span class="params">(String name)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取组成员</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 成员集合, 没有成员会返回 empty set</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    Set&lt;String&gt; <span class="title function_">getMembers</span><span class="params">(String name)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取组成员的 channel 集合, 只有在线的 channel 才会返回</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> name 组名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 成员 channel 集合</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    List&lt;Channel&gt; <span class="title function_">getMembersChannel</span><span class="params">(String name)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="3-2-聊天室业务-登录"><a href="#3-2-聊天室业务-登录" class="headerlink" title="3.2 聊天室业务-登录"></a>3.2 聊天室业务-登录</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatServer</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">boss</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="type">LoggingHandler</span> <span class="variable">LOGGING_HANDLER</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG);</span><br><span class="line">        <span class="type">MessageCodecSharable</span> <span class="variable">MESSAGE_CODEC</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MessageCodecSharable</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">ServerBootstrap</span> <span class="variable">serverBootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerBootstrap</span>();</span><br><span class="line">            serverBootstrap.channel(NioServerSocketChannel.class);</span><br><span class="line">            serverBootstrap.group(boss, worker);</span><br><span class="line">            serverBootstrap.childHandler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ProcotolFrameDecoder</span>());</span><br><span class="line">                    ch.pipeline().addLast(LOGGING_HANDLER);</span><br><span class="line">                    ch.pipeline().addLast(MESSAGE_CODEC);</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;LoginRequestMessage&gt;() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, LoginRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> msg.getUsername();</span><br><span class="line">                            <span class="type">String</span> <span class="variable">password</span> <span class="operator">=</span> msg.getPassword();</span><br><span class="line">                            <span class="type">boolean</span> <span class="variable">login</span> <span class="operator">=</span> UserServiceFactory.getUserService().login(username, password);</span><br><span class="line">                            LoginResponseMessage message;</span><br><span class="line">                            <span class="keyword">if</span>(login) &#123;</span><br><span class="line">                                message = <span class="keyword">new</span> <span class="title class_">LoginResponseMessage</span>(<span class="literal">true</span>, <span class="string">&quot;登录成功&quot;</span>);</span><br><span class="line">                            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                message = <span class="keyword">new</span> <span class="title class_">LoginResponseMessage</span>(<span class="literal">false</span>, <span class="string">&quot;用户名或密码不正确&quot;</span>);</span><br><span class="line">                            &#125;</span><br><span class="line">                            ctx.writeAndFlush(message);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">Channel</span> <span class="variable">channel</span> <span class="operator">=</span> serverBootstrap.bind(<span class="number">8080</span>).sync().channel();</span><br><span class="line">            channel.closeFuture().sync();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;server error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            boss.shutdownGracefully();</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatClient</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">group</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="type">LoggingHandler</span> <span class="variable">LOGGING_HANDLER</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG);</span><br><span class="line">        <span class="type">MessageCodecSharable</span> <span class="variable">MESSAGE_CODEC</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MessageCodecSharable</span>();</span><br><span class="line">        <span class="type">CountDownLatch</span> <span class="variable">WAIT_FOR_LOGIN</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">1</span>);</span><br><span class="line">        <span class="type">AtomicBoolean</span> <span class="variable">LOGIN</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicBoolean</span>(<span class="literal">false</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(group);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ProcotolFrameDecoder</span>());</span><br><span class="line"><span class="comment">//                    ch.pipeline().addLast(LOGGING_HANDLER);</span></span><br><span class="line">                    ch.pipeline().addLast(MESSAGE_CODEC);</span><br><span class="line">                    ch.pipeline().addLast(<span class="string">&quot;client handler&quot;</span>, <span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="comment">// 接收响应消息</span></span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;msg: &#123;&#125;&quot;</span>, msg);</span><br><span class="line">                            <span class="keyword">if</span> ((msg <span class="keyword">instanceof</span> LoginResponseMessage)) &#123;</span><br><span class="line">                                <span class="type">LoginResponseMessage</span> <span class="variable">response</span> <span class="operator">=</span> (LoginResponseMessage) msg;</span><br><span class="line">                                <span class="keyword">if</span> (response.isSuccess()) &#123;</span><br><span class="line">                                    <span class="comment">// 如果登录成功</span></span><br><span class="line">                                    LOGIN.set(<span class="literal">true</span>);</span><br><span class="line">                                &#125;</span><br><span class="line">                                <span class="comment">// 唤醒 system in 线程</span></span><br><span class="line">                                WAIT_FOR_LOGIN.countDown();</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                        <span class="comment">// 在连接建立后触发 active 事件</span></span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            <span class="comment">// 负责接收用户在控制台的输入，负责向服务器发送各种消息</span></span><br><span class="line">                            <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">                                <span class="type">Scanner</span> <span class="variable">scanner</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scanner</span>(System.in);</span><br><span class="line">                                System.out.println(<span class="string">&quot;请输入用户名:&quot;</span>);</span><br><span class="line">                                <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> scanner.nextLine();</span><br><span class="line">                                System.out.println(<span class="string">&quot;请输入密码:&quot;</span>);</span><br><span class="line">                                <span class="type">String</span> <span class="variable">password</span> <span class="operator">=</span> scanner.nextLine();</span><br><span class="line">                                <span class="comment">// 构造消息对象</span></span><br><span class="line">                                <span class="type">LoginRequestMessage</span> <span class="variable">message</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LoginRequestMessage</span>(username, password);</span><br><span class="line">                                <span class="comment">// 发送消息</span></span><br><span class="line">                                ctx.writeAndFlush(message);</span><br><span class="line">                                System.out.println(<span class="string">&quot;等待后续操作...&quot;</span>);</span><br><span class="line">                                <span class="keyword">try</span> &#123;</span><br><span class="line">                                    WAIT_FOR_LOGIN.await();</span><br><span class="line">                                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                                    e.printStackTrace();</span><br><span class="line">                                &#125;</span><br><span class="line">                                <span class="comment">// 如果登录失败</span></span><br><span class="line">                                <span class="keyword">if</span> (!LOGIN.get()) &#123;</span><br><span class="line">                                    ctx.channel().close();</span><br><span class="line">                                    <span class="keyword">return</span>;</span><br><span class="line">                                &#125;</span><br><span class="line">                                <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">                                    System.out.println(<span class="string">&quot;==================================&quot;</span>);</span><br><span class="line">                                    System.out.println(<span class="string">&quot;send [username] [content]&quot;</span>);</span><br><span class="line">                                    System.out.println(<span class="string">&quot;gsend [group name] [content]&quot;</span>);</span><br><span class="line">                                    System.out.println(<span class="string">&quot;gcreate [group name] [m1,m2,m3...]&quot;</span>);</span><br><span class="line">                                    System.out.println(<span class="string">&quot;gmembers [group name]&quot;</span>);</span><br><span class="line">                                    System.out.println(<span class="string">&quot;gjoin [group name]&quot;</span>);</span><br><span class="line">                                    System.out.println(<span class="string">&quot;gquit [group name]&quot;</span>);</span><br><span class="line">                                    System.out.println(<span class="string">&quot;quit&quot;</span>);</span><br><span class="line">                                    System.out.println(<span class="string">&quot;==================================&quot;</span>);</span><br><span class="line">                                    <span class="type">String</span> <span class="variable">command</span> <span class="operator">=</span> scanner.nextLine();</span><br><span class="line">                                    String[] s = command.split(<span class="string">&quot; &quot;</span>);</span><br><span class="line">                                    <span class="keyword">switch</span> (s[<span class="number">0</span>])&#123;</span><br><span class="line">                                        <span class="keyword">case</span> <span class="string">&quot;send&quot;</span>:</span><br><span class="line">                                            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">ChatRequestMessage</span>(username, s[<span class="number">1</span>], s[<span class="number">2</span>]));</span><br><span class="line">                                            <span class="keyword">break</span>;</span><br><span class="line">                                        <span class="keyword">case</span> <span class="string">&quot;gsend&quot;</span>:</span><br><span class="line">                                            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupChatRequestMessage</span>(username, s[<span class="number">1</span>], s[<span class="number">2</span>]));</span><br><span class="line">                                            <span class="keyword">break</span>;</span><br><span class="line">                                        <span class="keyword">case</span> <span class="string">&quot;gcreate&quot;</span>:</span><br><span class="line">                                            Set&lt;String&gt; set = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;(Arrays.asList(s[<span class="number">2</span>].split(<span class="string">&quot;,&quot;</span>)));</span><br><span class="line">                                            set.add(username); <span class="comment">// 加入自己</span></span><br><span class="line">                                            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupCreateRequestMessage</span>(s[<span class="number">1</span>], set));</span><br><span class="line">                                            <span class="keyword">break</span>;</span><br><span class="line">                                        <span class="keyword">case</span> <span class="string">&quot;gmembers&quot;</span>:</span><br><span class="line">                                            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupMembersRequestMessage</span>(s[<span class="number">1</span>]));</span><br><span class="line">                                            <span class="keyword">break</span>;</span><br><span class="line">                                        <span class="keyword">case</span> <span class="string">&quot;gjoin&quot;</span>:</span><br><span class="line">                                            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupJoinRequestMessage</span>(username, s[<span class="number">1</span>]));</span><br><span class="line">                                            <span class="keyword">break</span>;</span><br><span class="line">                                        <span class="keyword">case</span> <span class="string">&quot;gquit&quot;</span>:</span><br><span class="line">                                            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupQuitRequestMessage</span>(username, s[<span class="number">1</span>]));</span><br><span class="line">                                            <span class="keyword">break</span>;</span><br><span class="line">                                        <span class="keyword">case</span> <span class="string">&quot;quit&quot;</span>:</span><br><span class="line">                                            ctx.channel().close();</span><br><span class="line">                                            <span class="keyword">return</span>;</span><br><span class="line">                                    &#125;</span><br><span class="line">                                &#125;</span><br><span class="line">                            &#125;, <span class="string">&quot;system in&quot;</span>).start();</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">Channel</span> <span class="variable">channel</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>).sync().channel();</span><br><span class="line">            channel.closeFuture().sync();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            group.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="3-3-聊天室业务-单聊"><a href="#3-3-聊天室业务-单聊" class="headerlink" title="3.3 聊天室业务-单聊"></a>3.3 聊天室业务-单聊</h3><p>服务器端将 handler 独立出来</p>
<p>登录 handler</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LoginRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;LoginRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, LoginRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">username</span> <span class="operator">=</span> msg.getUsername();</span><br><span class="line">        <span class="type">String</span> <span class="variable">password</span> <span class="operator">=</span> msg.getPassword();</span><br><span class="line">        <span class="type">boolean</span> <span class="variable">login</span> <span class="operator">=</span> UserServiceFactory.getUserService().login(username, password);</span><br><span class="line">        LoginResponseMessage message;</span><br><span class="line">        <span class="keyword">if</span>(login) &#123;</span><br><span class="line">            SessionFactory.getSession().bind(ctx.channel(), username);</span><br><span class="line">            message = <span class="keyword">new</span> <span class="title class_">LoginResponseMessage</span>(<span class="literal">true</span>, <span class="string">&quot;登录成功&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            message = <span class="keyword">new</span> <span class="title class_">LoginResponseMessage</span>(<span class="literal">false</span>, <span class="string">&quot;用户名或密码不正确&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        ctx.writeAndFlush(message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>单聊 handler</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ChatRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;ChatRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, ChatRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">to</span> <span class="operator">=</span> msg.getTo();</span><br><span class="line">        <span class="type">Channel</span> <span class="variable">channel</span> <span class="operator">=</span> SessionFactory.getSession().getChannel(to);</span><br><span class="line">        <span class="comment">// 在线</span></span><br><span class="line">        <span class="keyword">if</span>(channel != <span class="literal">null</span>) &#123;</span><br><span class="line">            channel.writeAndFlush(<span class="keyword">new</span> <span class="title class_">ChatResponseMessage</span>(msg.getFrom(), msg.getContent()));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 不在线</span></span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">ChatResponseMessage</span>(<span class="literal">false</span>, <span class="string">&quot;对方用户不存在或者不在线&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="3-4-聊天室业务-群聊"><a href="#3-4-聊天室业务-群聊" class="headerlink" title="3.4 聊天室业务-群聊"></a>3.4 聊天室业务-群聊</h3><p>创建群聊</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupCreateRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;GroupCreateRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, GroupCreateRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">groupName</span> <span class="operator">=</span> msg.getGroupName();</span><br><span class="line">        Set&lt;String&gt; members = msg.getMembers();</span><br><span class="line">        <span class="comment">// 群管理器</span></span><br><span class="line">        <span class="type">GroupSession</span> <span class="variable">groupSession</span> <span class="operator">=</span> GroupSessionFactory.getGroupSession();</span><br><span class="line">        <span class="type">Group</span> <span class="variable">group</span> <span class="operator">=</span> groupSession.createGroup(groupName, members);</span><br><span class="line">        <span class="keyword">if</span> (group == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 发生成功消息</span></span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupCreateResponseMessage</span>(<span class="literal">true</span>, groupName + <span class="string">&quot;创建成功&quot;</span>));</span><br><span class="line">            <span class="comment">// 发送拉群消息</span></span><br><span class="line">            List&lt;Channel&gt; channels = groupSession.getMembersChannel(groupName);</span><br><span class="line">            <span class="keyword">for</span> (Channel channel : channels) &#123;</span><br><span class="line">                channel.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupCreateResponseMessage</span>(<span class="literal">true</span>, <span class="string">&quot;您已被拉入&quot;</span> + groupName));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupCreateResponseMessage</span>(<span class="literal">false</span>, groupName + <span class="string">&quot;已经存在&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>群聊</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupChatRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;GroupChatRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, GroupChatRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        List&lt;Channel&gt; channels = GroupSessionFactory.getGroupSession()</span><br><span class="line">                .getMembersChannel(msg.getGroupName());</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (Channel channel : channels) &#123;</span><br><span class="line">            channel.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupChatResponseMessage</span>(msg.getFrom(), msg.getContent()));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>加入群聊</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupJoinRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;GroupJoinRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, GroupJoinRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">Group</span> <span class="variable">group</span> <span class="operator">=</span> GroupSessionFactory.getGroupSession().joinMember(msg.getGroupName(), msg.getUsername());</span><br><span class="line">        <span class="keyword">if</span> (group != <span class="literal">null</span>) &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupJoinResponseMessage</span>(<span class="literal">true</span>, msg.getGroupName() + <span class="string">&quot;群加入成功&quot;</span>));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupJoinResponseMessage</span>(<span class="literal">true</span>, msg.getGroupName() + <span class="string">&quot;群不存在&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>退出群聊</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupQuitRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;GroupQuitRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, GroupQuitRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">Group</span> <span class="variable">group</span> <span class="operator">=</span> GroupSessionFactory.getGroupSession().removeMember(msg.getGroupName(), msg.getUsername());</span><br><span class="line">        <span class="keyword">if</span> (group != <span class="literal">null</span>) &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupJoinResponseMessage</span>(<span class="literal">true</span>, <span class="string">&quot;已退出群&quot;</span> + msg.getGroupName()));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupJoinResponseMessage</span>(<span class="literal">true</span>, msg.getGroupName() + <span class="string">&quot;群不存在&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>查看成员</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GroupMembersRequestMessageHandler</span> <span class="keyword">extends</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;GroupMembersRequestMessage&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, GroupMembersRequestMessage msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        Set&lt;String&gt; members = GroupSessionFactory.getGroupSession()</span><br><span class="line">                .getMembers(msg.getGroupName());</span><br><span class="line">        ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">GroupMembersResponseMessage</span>(members));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="3-5-聊天室业务-退出"><a href="#3-5-聊天室业务-退出" class="headerlink" title="3.5 聊天室业务-退出"></a>3.5 聊天室业务-退出</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">@Slf4j</span><br><span class="line">@ChannelHandler.Sharable</span><br><span class="line">public class QuitHandler extends ChannelInboundHandlerAdapter &#123;</span><br><span class="line"></span><br><span class="line">    // 当连接断开时触发 inactive 事件</span><br><span class="line">    @Override</span><br><span class="line">    public void channelInactive(ChannelHandlerContext ctx) throws Exception &#123;</span><br><span class="line">        SessionFactory.getSession().unbind(ctx.channel());</span><br><span class="line">        log.debug(&quot;&#123;&#125; 已经断开&quot;, ctx.channel());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">	// 当出现异常时触发</span><br><span class="line">    @Override</span><br><span class="line">    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception &#123;</span><br><span class="line">        SessionFactory.getSession().unbind(ctx.channel());</span><br><span class="line">        log.debug(&quot;&#123;&#125; 已经异常断开 异常是&#123;&#125;&quot;, ctx.channel(), cause.getMessage());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="3-6-聊天室业务-空闲检测"><a href="#3-6-聊天室业务-空闲检测" class="headerlink" title="3.6 聊天室业务-空闲检测"></a>3.6 聊天室业务-空闲检测</h3><h4 id="连接假死"><a href="#连接假死" class="headerlink" title="连接假死"></a>连接假死</h4><p>原因</p>
<ul>
<li>网络设备出现故障，例如网卡，机房等，底层的 TCP 连接已经断开了，但应用程序没有感知到，仍然占用着资源。</li>
<li>公网网络不稳定，出现丢包。如果连续出现丢包，这时现象就是客户端数据发不出去，服务端也一直收不到数据，就这么一直耗着</li>
<li>应用程序线程阻塞，无法进行数据读写</li>
</ul>
<p>问题</p>
<ul>
<li>假死的连接占用的资源不能自动释放</li>
<li>向假死的连接发送数据，得到的反馈是发送超时</li>
</ul>
<p>服务器端解决</p>
<ul>
<li>怎么判断客户端连接是否假死呢？如果能收到客户端数据，说明没有假死。因此策略就可以定为，每隔一段时间就检查这段时间内是否接收到客户端数据，没有就可以判定为连接假死</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用来判断是不是 读空闲时间过长，或 写空闲时间过长</span></span><br><span class="line"><span class="comment">// 5s 内如果没有收到 channel 的数据，会触发一个 IdleState#READER_IDLE 事件</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">IdleStateHandler</span>(<span class="number">5</span>, <span class="number">0</span>, <span class="number">0</span>));</span><br><span class="line"><span class="comment">// ChannelDuplexHandler 可以同时作为入站和出站处理器</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelDuplexHandler</span>() &#123;</span><br><span class="line">    <span class="comment">// 用来触发特殊事件</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">userEventTriggered</span><span class="params">(ChannelHandlerContext ctx, Object evt)</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line">        <span class="type">IdleStateEvent</span> <span class="variable">event</span> <span class="operator">=</span> (IdleStateEvent) evt;</span><br><span class="line">        <span class="comment">// 触发了读空闲事件</span></span><br><span class="line">        <span class="keyword">if</span> (event.state() == IdleState.READER_IDLE) &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;已经 5s 没有读到数据了&quot;</span>);</span><br><span class="line">            ctx.channel().close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<p>客户端定时心跳</p>
<ul>
<li>客户端可以定时向服务器端发送数据，只要这个时间间隔小于服务器定义的空闲检测的时间间隔，那么就能防止前面提到的误判，客户端可以定义如下心跳处理器</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用来判断是不是 读空闲时间过长，或 写空闲时间过长</span></span><br><span class="line"><span class="comment">// 3s 内如果没有向服务器写数据，会触发一个 IdleState#WRITER_IDLE 事件</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">IdleStateHandler</span>(<span class="number">0</span>, <span class="number">3</span>, <span class="number">0</span>));</span><br><span class="line"><span class="comment">// ChannelDuplexHandler 可以同时作为入站和出站处理器</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelDuplexHandler</span>() &#123;</span><br><span class="line">    <span class="comment">// 用来触发特殊事件</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">userEventTriggered</span><span class="params">(ChannelHandlerContext ctx, Object evt)</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line">        <span class="type">IdleStateEvent</span> <span class="variable">event</span> <span class="operator">=</span> (IdleStateEvent) evt;</span><br><span class="line">        <span class="comment">// 触发了写空闲事件</span></span><br><span class="line">        <span class="keyword">if</span> (event.state() == IdleState.WRITER_IDLE) &#123;</span><br><span class="line">            <span class="comment">//                                log.debug(&quot;3s 没有写数据了，发送一个心跳包&quot;);</span></span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">PingMessage</span>());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>





</article><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/Java/">Java</a><a class="post-meta__tags" href="/tags/Netty/">Netty</a></div><div class="post_share"><div class="social-share" data-image="http://qiniu.goku.top/blog/netty.png" data-sites="wechat,weibo,qq,facebook,twitter"></div><link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/social-share.js/1.0.16/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.bootcdn.net/ajax/libs/social-share.js/1.0.16/js/social-share.min.js" defer></script></div></div><div class="post-reward"><div class="reward-button"><i class="fas fa-qrcode"></i> 打赏</div><div class="reward-main"><ul class="reward-all"><li class="reward-item"><a href="/img/wxpay.jpeg" target="_blank"><img class="post-qr-code-img" src="/img/wxpay.jpeg" alt="微信"/></a><div class="post-qr-code-desc">微信</div></li><li class="reward-item"><a href="/img/alipay.jpeg" target="_blank"><img class="post-qr-code-img" src="/img/alipay.jpeg" alt="支付宝"/></a><div class="post-qr-code-desc">支付宝</div></li></ul></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/posts/47670.html"><img class="prev-cover" src="http://qiniu.goku.top/blog/netty.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Netty-优化与源码</div></div></a></div><div class="next-post pull-right"><a href="/posts/51310.html"><img class="next-cover" src="http://qiniu.goku.top/blog/netty.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Netty-入门</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/posts/47670.html" title="Netty-优化与源码"><img class="cover" src="http://qiniu.goku.top/blog/netty.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-04-11</div><div class="title">Netty-优化与源码</div></div></a></div><div><a href="/posts/30.html" title="Netty-nio"><img class="cover" src="http://qiniu.goku.top/blog/netty.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-04-11</div><div class="title">Netty-nio</div></div></a></div><div><a href="/posts/51310.html" title="Netty-入门"><img class="cover" src="http://qiniu.goku.top/blog/netty.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-04-11</div><div class="title">Netty-入门</div></div></a></div><div><a href="/posts/53914.html" title="JVM学习-垃圾回收"><img class="cover" src="http://qiniu.goku.top/blog/jvm.jpeg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-12-04</div><div class="title">JVM学习-垃圾回收</div></div></a></div><div><a href="/posts/9365.html" title="JVM学习-类加载"><img class="cover" src="http://qiniu.goku.top/blog/jvm.jpeg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-02-23</div><div class="title">JVM学习-类加载</div></div></a></div><div><a href="/posts/8275.html" title="Spring中使用模板方法模式(接上文)"><img class="cover" src="http://qiniu.goku.top/blog/post_banner_6.jpeg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-12-13</div><div class="title">Spring中使用模板方法模式(接上文)</div></div></a></div></div></div><hr/><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="fas fa-comments fa-fw"></i><span> 评论</span></div></div><div class="comment-wrap"><div><div id="twikoo-wrap"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src="https://thirdqq.qlogo.cn/g?b=sdk&amp;k=C88w6ZftQk9ibgdcM4sH4jg&amp;s=140&amp;t=1657271033" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">Pei</div><div class="author-info__description">躺平虽然不好，但真的很爽</div></div><div class="card-info-data is-center"><div class="card-info-data-item"><a href="/archives/"><div class="headline">文章</div><div class="length-num">38</div></a></div><div class="card-info-data-item"><a href="/tags/"><div class="headline">标签</div><div class="length-num">23</div></a></div><div class="card-info-data-item"><a href="/categories/"><div class="headline">分类</div><div class="length-num">20</div></a></div></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/sgr997"><i class="fab fa-github"></i><span>联系我</span></a><div class="card-info-social-icons is-center"><a class="social-icon" href="https://github.com/sgr997" target="_blank" title="Github"><i class="fab fa-github"></i></a><a class="social-icon" href="http://www.coolapk.com/u/725902" target="_blank" title="酷安"><i class="fa-solid fa-c"></i></a><a class="social-icon" href="https://space.bilibili.com/49499302" target="_blank" title="bilibili"><i class="fa-brands fa-bilibili"></i></a></div></div><script src="https://npm.elemecdn.com/ethan4116-blog/lib/js/other/two-people/twopeople1.js"></script><script src="https://npm.elemecdn.com/ethan4116-blog/lib/js/other/two-people/zdog.dist.js"></script><script id="rendered-js" src="https://npm.elemecdn.com/ethan4116-blog/lib/js/other/two-people/twopeople.js"></script><style>.card-widget.card-announcement {
margin: 0;
align-items: center;
justify-content: center;
text-align: center;
}
canvas {
display: block;
margin: 0 auto;
cursor: move;
}</style><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E4%B8%89-Netty-%E8%BF%9B%E9%98%B6"><span class="toc-number">1.</span> <span class="toc-text">三. Netty 进阶</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#1-%E7%B2%98%E5%8C%85%E4%B8%8E%E5%8D%8A%E5%8C%85"><span class="toc-number">1.1.</span> <span class="toc-text">1. 粘包与半包</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-1-%E7%B2%98%E5%8C%85%E7%8E%B0%E8%B1%A1"><span class="toc-number">1.1.1.</span> <span class="toc-text">1.1 粘包现象</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-2-%E5%8D%8A%E5%8C%85%E7%8E%B0%E8%B1%A1"><span class="toc-number">1.1.2.</span> <span class="toc-text">1.2 半包现象</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-3-%E7%8E%B0%E8%B1%A1%E5%88%86%E6%9E%90"><span class="toc-number">1.1.3.</span> <span class="toc-text">1.3 现象分析</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1-4-%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88"><span class="toc-number">1.1.4.</span> <span class="toc-text">1.4 解决方案</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%B9%E6%B3%951%EF%BC%8C%E7%9F%AD%E9%93%BE%E6%8E%A5"><span class="toc-number">1.1.4.1.</span> <span class="toc-text">方法1，短链接</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%B9%E6%B3%952%EF%BC%8C%E5%9B%BA%E5%AE%9A%E9%95%BF%E5%BA%A6"><span class="toc-number">1.1.4.2.</span> <span class="toc-text">方法2，固定长度</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%B9%E6%B3%953%EF%BC%8C%E5%9B%BA%E5%AE%9A%E5%88%86%E9%9A%94%E7%AC%A6"><span class="toc-number">1.1.4.3.</span> <span class="toc-text">方法3，固定分隔符</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%B9%E6%B3%954%EF%BC%8C%E9%A2%84%E8%AE%BE%E9%95%BF%E5%BA%A6"><span class="toc-number">1.1.4.4.</span> <span class="toc-text">方法4，预设长度</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-%E5%8D%8F%E8%AE%AE%E8%AE%BE%E8%AE%A1%E4%B8%8E%E8%A7%A3%E6%9E%90"><span class="toc-number">1.2.</span> <span class="toc-text">2. 协议设计与解析</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#2-1-%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E5%8D%8F%E8%AE%AE%EF%BC%9F"><span class="toc-number">1.2.1.</span> <span class="toc-text">2.1 为什么需要协议？</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-redis-%E5%8D%8F%E8%AE%AE%E4%B8%BE%E4%BE%8B"><span class="toc-number">1.2.2.</span> <span class="toc-text">2.2 redis 协议举例</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-3-http-%E5%8D%8F%E8%AE%AE%E4%B8%BE%E4%BE%8B"><span class="toc-number">1.2.3.</span> <span class="toc-text">2.3 http 协议举例</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-4-%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8D%8F%E8%AE%AE%E8%A6%81%E7%B4%A0"><span class="toc-number">1.2.4.</span> <span class="toc-text">2.4 自定义协议要素</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8"><span class="toc-number">1.2.4.1.</span> <span class="toc-text">编解码器</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%F0%9F%92%A1-%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E5%8F%AF%E4%BB%A5%E5%8A%A0-Sharable"><span class="toc-number">1.2.4.2.</span> <span class="toc-text">💡 什么时候可以加 @Sharable</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-%E8%81%8A%E5%A4%A9%E5%AE%A4%E6%A1%88%E4%BE%8B"><span class="toc-number">1.3.</span> <span class="toc-text">3. 聊天室案例</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#3-1-%E8%81%8A%E5%A4%A9%E5%AE%A4%E4%B8%9A%E5%8A%A1%E4%BB%8B%E7%BB%8D"><span class="toc-number">1.3.1.</span> <span class="toc-text">3.1 聊天室业务介绍</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-2-%E8%81%8A%E5%A4%A9%E5%AE%A4%E4%B8%9A%E5%8A%A1-%E7%99%BB%E5%BD%95"><span class="toc-number">1.3.2.</span> <span class="toc-text">3.2 聊天室业务-登录</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-3-%E8%81%8A%E5%A4%A9%E5%AE%A4%E4%B8%9A%E5%8A%A1-%E5%8D%95%E8%81%8A"><span class="toc-number">1.3.3.</span> <span class="toc-text">3.3 聊天室业务-单聊</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-4-%E8%81%8A%E5%A4%A9%E5%AE%A4%E4%B8%9A%E5%8A%A1-%E7%BE%A4%E8%81%8A"><span class="toc-number">1.3.4.</span> <span class="toc-text">3.4 聊天室业务-群聊</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-5-%E8%81%8A%E5%A4%A9%E5%AE%A4%E4%B8%9A%E5%8A%A1-%E9%80%80%E5%87%BA"><span class="toc-number">1.3.5.</span> <span class="toc-text">3.5 聊天室业务-退出</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-6-%E8%81%8A%E5%A4%A9%E5%AE%A4%E4%B8%9A%E5%8A%A1-%E7%A9%BA%E9%97%B2%E6%A3%80%E6%B5%8B"><span class="toc-number">1.3.6.</span> <span class="toc-text">3.6 聊天室业务-空闲检测</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%BF%9E%E6%8E%A5%E5%81%87%E6%AD%BB"><span class="toc-number">1.3.6.1.</span> <span class="toc-text">连接假死</span></a></li></ol></li></ol></li></ol></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/31488961.html" title="解决macos安装软件后提示已损坏问题">解决macos安装软件后提示已损坏问题</a><time datetime="2023-10-10T05:22:13.909Z" title="发表于 2023-10-10 13:22:13">2023-10-10</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/77213149.html" title="李跳跳自定义规则">李跳跳自定义规则</a><time datetime="2023-09-16T05:02:41.000Z" title="发表于 2023-09-16 13:02:41">2023-09-16</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/AI%E8%81%8A%E5%A4%A9%E7%BD%91%E7%AB%99.html" title="AI聊天网站推荐">AI聊天网站推荐</a><time datetime="2023-06-19T11:39:41.000Z" title="发表于 2023-06-19 19:39:41">2023-06-19</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/dd74890d.html" title="随记账本项目">随记账本项目</a><time datetime="2023-04-27T02:40:25.000Z" title="发表于 2023-04-27 10:40:25">2023-04-27</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/fc4d392a.html" title="一场烟花送给你">一场烟花送给你</a><time datetime="2023-02-18T13:34:14.000Z" title="发表于 2023-02-18 21:34:14">2023-02-18</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div id="ft"><div class="ft-item-1"><div class="t-top"><div class="t-t-l"><p class="ft-t t-l-t">关于博客</p><div class="bg-ad"><div>衷心感谢每一位光临我的博客的朋友</div><div class="btn-xz-box"><a class="btn-xz" target="_blank" rel="noopener" href="https://gg.goku.top">聊天问答</a></div></div></div><div class="t-t-r"><p class="ft-t t-l-t">推荐网址</p><ul class="ft-links"><li><a target="_blank" rel="noopener" href="https://github.com/sgr997">Github</a><a href="/webstack.html">网址导航</a></li><li><a target="_blank" rel="noopener" href="http://qiniu.goku.top/wxpay.jpeg">来杯咖啡</a><a href="/artitalk/">留点什么</a></li><li><a href="/about/">关于博主</a><a href="/archives/">文章归档</a></li><li><a href="/categories/">文章分类</a><a href="/tags/">文章标签</a></li></ul></div></div></div><div class="ft-item-2"><p class="ft-t">推荐友链</p><div class="ft-img-group"><div class="img-group-item"><a href="/webstack.html"><img src="http://qiniu.goku.top/blog/icon.png" alt="网址导航"/></a></div></div></div></div><div class="copyright">&copy;2020 - 2023  <i id="heartbeat" class="fa fas fa-heartbeat"></i> Pei</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div><div class="footer_custom_text"><a href="https://beian.miit.gov.cn/" target="_blank">豫ICP备2023023872号</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="fas fa-comments"></i></a><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">本地搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/fancyapps-ui/5.0.22/fancybox/fancybox.umd.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/vanilla-lazyload/17.8.4/lazyload.iife.min.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/snackbarjs/1.1.0/snackbar.min.js"></script><script src="/js/search/local-search.js"></script><script>var preloader = {
  endLoading: () => {
    document.body.style.overflow = 'auto';
    document.getElementById('loading-box').classList.add("loaded")
  },
  initLoading: () => {
    document.body.style.overflow = '';
    document.getElementById('loading-box').classList.remove("loaded")

  }
}
window.addEventListener('load',preloader.endLoading())</script><div class="js-pjax"><script>(() => {
  const $mermaidWrap = document.querySelectorAll('#article-container .mermaid-wrap')
  if ($mermaidWrap.length) {
    window.runMermaid = () => {
      window.loadMermaid = true
      const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? '' : ''

      Array.from($mermaidWrap).forEach((item, index) => {
        const mermaidSrc = item.firstElementChild
        const mermaidThemeConfig = '%%{init:{ \'theme\':\'' + theme + '\'}}%%\n'
        const mermaidID = 'mermaid-' + index
        const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent
        mermaid.mermaidAPI.render(mermaidID, mermaidDefinition, (svgCode) => {
          mermaidSrc.insertAdjacentHTML('afterend', svgCode)
        })
      })
    }

    const loadMermaid = () => {
      window.loadMermaid ? runMermaid() : getScript('https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js').then(runMermaid)
    }

    window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid)
  }
})()</script><script>(()=>{
  const init = () => {
    twikoo.init(Object.assign({
      el: '#twikoo-wrap',
      envId: 'https://twikoo.goku.top/',
      region: '',
      onCommentLoaded: function () {
        btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.vemoji)'))
      }
    }, null))
  }

  const getCount = () => {
    twikoo.getCommentsCount({
      envId: 'https://twikoo.goku.top/',
      region: '',
      urls: [window.location.pathname],
      includeReply: false
    }).then(function (res) {
      document.getElementById('twikoo-count').innerText = res[0].count
    }).catch(function (err) {
      console.error(err);
    });
  }

  const runFn = () => {
    init()
    
  }

  const loadTwikoo = () => {
    if (typeof twikoo === 'object') {
      setTimeout(runFn,0)
      return
    } 
    getScript('https://cdn.bootcdn.net/ajax/libs/twikoo/1.6.20/twikoo.all.min.js').then(runFn)
  }

  if ('Twikoo' === 'Twikoo' || !true) {
    if (true) btf.loadComment(document.getElementById('twikoo-wrap'), loadTwikoo)
    else loadTwikoo()
  } else {
    window.loadOtherComment = () => {
      loadTwikoo()
    }
  }
})()</script></div><script>window.addEventListener('load', () => {
  const changeContent = (content) => {
    if (content === '') return content

    content = content.replace(/<img.*?src="(.*?)"?[^\>]+>/ig, '[图片]') // replace image link
    content = content.replace(/<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi, '[链接]') // replace url
    content = content.replace(/<pre><code>.*?<\/pre>/gi, '[代码]') // replace code
    content = content.replace(/<[^>]+>/g,"") // remove html tag

    if (content.length > 150) {
      content = content.substring(0,150) + '...'
    }
    return content
  }

  const getComment = () => {
    const runTwikoo = () => {
      twikoo.getRecentComments({
        envId: 'https://twikoo.goku.top/',
        region: '',
        pageSize: 6,
        includeReply: true
      }).then(function (res) {
        const twikooArray = res.map(e => {
          return {
            'content': changeContent(e.comment),
            'avatar': e.avatar,
            'nick': e.nick,
            'url': e.url + '#' + e.id,
            'date': new Date(e.created).toISOString()
          }
        })

        saveToLocal.set('twikoo-newest-comments', JSON.stringify(twikooArray), 10/(60*24))
        generateHtml(twikooArray)
      }).catch(function (err) {
        const $dom = document.querySelector('#card-newest-comments .aside-list')
        $dom.innerHTML= "无法获取评论，请确认相关配置是否正确"
      })
    }

    if (typeof twikoo === 'object') {
      runTwikoo()
    } else {
      getScript('https://cdn.bootcdn.net/ajax/libs/twikoo/1.6.20/twikoo.all.min.js').then(runTwikoo)
    }
  }

  const generateHtml = array => {
    let result = ''

    if (array.length) {
      for (let i = 0; i < array.length; i++) {
        result += '<div class=\'aside-list-item\'>'

        if (false) {
          const name = 'data-lazy-src'
          result += `<a href='${array[i].url}' class='thumbnail'><img ${name}='${array[i].avatar}' alt='${array[i].nick}'></a>`
        }
        
        result += `<div class='content'>
        <a class='comment' href='${array[i].url}' title='${array[i].content}'>${array[i].content}</a>
        <div class='name'><span>${array[i].nick} / </span><time datetime="${array[i].date}">${btf.diffDate(array[i].date, true)}</time></div>
        </div></div>`
      }
    } else {
      result += '没有评论'
    }

    let $dom = document.querySelector('#card-newest-comments .aside-list')
    $dom.innerHTML= result
    window.lazyLoadInstance && window.lazyLoadInstance.update()
    window.pjax && window.pjax.refresh($dom)
  }

  const newestCommentInit = () => {
    if (document.querySelector('#card-newest-comments .aside-list')) {
      const data = saveToLocal.get('twikoo-newest-comments')
      if (data) {
        generateHtml(JSON.parse(data))
      } else {
        getComment()
      }
    }
  }

  newestCommentInit()
  document.addEventListener('pjax:complete', newestCommentInit)
})</script><canvas id="universe"></canvas><script defer src="/html/js/universe.js"></script><script defer src="/html/js/disablef12.js"></script><script src="/html/js/lunar.js"></script><script src="/html/js/day.js"></script><script type="text/javascript" src="https://cdn1.tianli0.top/npm/sweetalert2@8.19.0/dist/sweetalert2.all.js"></script><script async src="/html/js/justlovesmile.js"></script><script>let tianliGPT_postSelector = '\#post \#article-container';let tianliGPT_postURL = '\*/posts/\*';let tianliGPT_key = '123';</script><script data-pjax src="/html/js/summ.min.js"></script><script defer="defer" id="fluttering_ribbon" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/dist/canvas-fluttering-ribbon.min.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/pjax/0.2.8/pjax.min.js"></script><script>let pjaxSelectors = ["title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"]

var pjax = new Pjax({
  elements: 'a:not([target="_blank"])',
  selectors: pjaxSelectors,
  cacheBust: false,
  analytics: false,
  scrollRestoration: false
})

document.addEventListener('pjax:send', function () {

  // removeEventListener scroll 
  window.tocScrollFn && window.removeEventListener('scroll', window.tocScrollFn)
  window.scrollCollect && window.removeEventListener('scroll', scrollCollect)

  typeof preloader === 'object' && preloader.initLoading()
  document.getElementById('rightside').style.cssText = "opacity: ''; transform: ''"
  
  if (window.aplayers) {
    for (let i = 0; i < window.aplayers.length; i++) {
      if (!window.aplayers[i].options.fixed) {
        window.aplayers[i].destroy()
      }
    }
  }

  typeof typed === 'object' && typed.destroy()

  //reset readmode
  const $bodyClassList = document.body.classList
  $bodyClassList.contains('read-mode') && $bodyClassList.remove('read-mode')

})

document.addEventListener('pjax:complete', function () {
  window.refreshFn()

  document.querySelectorAll('script[data-pjax]').forEach(item => {
    const newScript = document.createElement('script')
    const content = item.text || item.textContent || item.innerHTML || ""
    Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value))
    newScript.appendChild(document.createTextNode(content))
    item.parentNode.replaceChild(newScript, item)
  })

  GLOBAL_CONFIG.islazyload && window.lazyLoadInstance.update()

  typeof chatBtnFn === 'function' && chatBtnFn()
  typeof panguInit === 'function' && panguInit()

  // google analytics
  typeof gtag === 'function' && gtag('config', '', {'page_path': window.location.pathname});

  // baidu analytics
  typeof _hmt === 'object' && _hmt.push(['_trackPageview',window.location.pathname]);

  typeof loadMeting === 'function' && document.getElementsByClassName('aplayer').length && loadMeting()

  // prismjs
  typeof Prism === 'object' && Prism.highlightAll()

  typeof preloader === 'object' && preloader.endLoading()
})

document.addEventListener('pjax:error', (e) => {
  if (e.request.status === 404) {
    pjax.loadUrl('/404.html')
  }
})</script></div></body></html>